/**
 *  Copyright 2007-2008 University Of Southern California
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */


package edu.isi.pegasus.planner.code.generator;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import edu.isi.pegasus.common.logging.LogManager;

import edu.isi.pegasus.planner.classes.ADag;
import edu.isi.pegasus.planner.classes.Job;
import edu.isi.pegasus.planner.classes.PegasusBag;

import edu.isi.pegasus.planner.cluster.JobAggregator;
import edu.isi.pegasus.planner.cluster.aggregator.JobAggregatorFactory;
import edu.isi.pegasus.planner.cluster.aggregator.MPIExec;

import edu.isi.pegasus.planner.code.CodeGeneratorException;
import edu.isi.pegasus.planner.code.GridStart;
import edu.isi.pegasus.planner.code.GridStartFactory;

import edu.isi.pegasus.planner.namespace.Pegasus;

import edu.isi.pegasus.planner.partitioner.graph.Adapter;
import edu.isi.pegasus.planner.partitioner.graph.Graph;
import edu.isi.pegasus.planner.partitioner.graph.GraphNode;

/**
 * This code generator generates a shell script in the submit directory.
 * The shell script can be executed on the submit host to run the workflow
 * locally.
 *
 * @author Karan Vahi
 * @version $Revision$
 */
public class PMC extends Abstract {

    /**
     * The handle to the GridStart Factory.
     */
    protected GridStartFactory mGridStartFactory;

    /**
     * A boolean indicating whether grid start has been initialized or not.
     */
    protected boolean mInitializeGridStart;
    
    /**
     * Handle to the PBS Code generator.
     */
    private final PBS mPBS;

    /**
     * The default constructor.
     */
    public PMC( ){
        super();
        mInitializeGridStart = true;
        mGridStartFactory = new GridStartFactory();
        //instantiate the PBS Code generator.
        //we may need a factory later on
        mPBS = new PBS();
    }

    /**
     * Initializes the Code Generator implementation.
     *
     *  @param bag   the bag of initialization objects.
     *
     * @throws CodeGeneratorException in case of any error occuring code generation.
     */
    public void initialize( PegasusBag bag ) throws CodeGeneratorException{
        super.initialize( bag );
        mPBS.initialize( bag );
        mLogger = bag.getLogger();


        //create the base directory recovery
        File wdir = new File(mSubmitFileDir);
        wdir.mkdirs();


    }

    /**
     * Generates the code for the concrete workflow in the GRMS input format.
     * The GRMS input format is xml based. One XML file is generated per
     * workflow.
     *
     * @param dag  the concrete workflow.
     *
     * @return handle to the PMC file generated in the submit directory.
     *
     * @throws CodeGeneratorException in case of any error occuring code generation.
     */
    public Collection<File> generateCode( ADag dag ) throws CodeGeneratorException{
        Collection result = new ArrayList( 1 );

//        this.writeOutStampedeEvents( dag );

        //we first need to convert internally into graph format
        Graph workflow =    Adapter.convert( dag );

        mGridStartFactory.initialize(mBag, dag);

        Job prevJob = null;
        //traverse the workflow and enable the jobs with kickstart first
        for( Iterator<GraphNode> it = workflow.nodeIterator(); it.hasNext(); ){
            GraphNode node = it.next();
            Job job = (Job)node.getContent();
            String site = job.getSiteHandle();

            //sanity check
            if( !( prevJob == null || prevJob.getSiteHandle().equalsIgnoreCase( site ) )){
                StringBuffer error = new StringBuffer();
                error.append( "Site Mismatch between jobs " ).append( "(" ).
                      append( job.getID() ).append( ":" ).append( site ).append( "," ).
                      append( prevJob.getID() ).append( ":" ).append( prevJob.getSiteHandle() ).
                      append( ") ").append( "." ).
                      append( "For the PMC Code generator all jobs should be mapped to the same site. ");
                throw new CodeGeneratorException( error.toString() );
            }

            GridStart gridStart = mGridStartFactory.loadGridStart( job , null );

            //trigger the -w option for kickstart always
            job.vdsNS.construct( Pegasus.CHANGE_DIR_KEY , "true" );

            //the stdin of the job is handled outside of
            //kickstart module for time being.
            //assumption is that we are always using kickstart
            //for this scenario
            String stdin = job.getStdIn();
            StringBuffer kickstartPreArgs = new StringBuffer();
            boolean prepend = false;
            if( stdin == null || stdin.length() == 0 ){
                //nothing to do
            }
            else{
                //construct the kickstart arguments for connecting
                // the task stdin
                kickstartPreArgs.append("-i ");
                if( stdin.startsWith( File.separator ) ){
                    kickstartPreArgs.append( stdin );
                }
                else{
                    //prepend the submit dirctory
                    kickstartPreArgs.append( mSubmitFileDir ).append( File.separator).append( stdin );
                }
                kickstartPreArgs.append(' ');
                //reset stdin as we don't want
                //kickstart module to handle it
                job.setStdIn( "" );
                prepend = true;
            }

            //enable the job
            if( !gridStart.enable( job,false ) ){
                String msg = "Job " +  job.getName() + " cannot be enabled by " +
                             gridStart.shortDescribe() + " to run at " +
                             job.getSiteHandle();
                mLogger.log( msg, LogManager.FATAL_MESSAGE_LEVEL );
                throw new CodeGeneratorException( msg );
            }

            if( prepend ){
                //job has already been kickstarted.
                //prepend the -i option for stdin
                StringBuffer args = new StringBuffer();
                args.append( kickstartPreArgs ).append( job.getArguments() );
                job.setArguments( args.toString() );
            }

            prevJob = job;

        }

        //lets load the PMC cluster implementation
        //and generate the PMC file for it
        JobAggregator aggregator = JobAggregatorFactory.loadInstance( JobAggregatorFactory.MPI_EXEC_CLASS, dag, mBag);
        MPIExec pmcAggregator = (MPIExec)aggregator;
        String name = pmcBasename( dag );
        //PM-660 designate that the graph is for the whole workflow
        pmcAggregator.generatePMCInputFile(workflow, name, false );

        //lets generate the PBS input file
        mPBS.generateCode( dag );

         //the dax replica store
        this.writeOutDAXReplicaStore( dag );

        //write out the braindump file
        this.writeOutBraindump( dag );

        //write out the nelogger file
        this.writeOutStampedeEvents( dag );

        //write out the metrics file
//        this.writeOutWorkflowMetrics(dag);

        return result;
    }

    /**
     * Generates the code for a single job in the input format of the workflow
     * executor being used.
     *
     * @param dag    the dag of which the job is a part of.
     * @param job    the <code>Job</code> object holding the information about
     *               that particular job.
     *
     * @throws CodeGeneratorException in case of any error occuring code generation.
     */
    public void generateCode( ADag dag, Job job ) throws CodeGeneratorException{
       throw new CodeGeneratorException( "The code generator only works on the workflow level" );
    }   
    
    /**
     * Returns a Map containing additional braindump entries that are specific
     * to a Code Generator. The entries added for this are from the scheduler
     * specific generator
     * 
     * @param workflow  the executable workflow
     * 
     * @return Map
     */
    public Map<String, String> getAdditionalBraindumpEntries( ADag workflow ) {
        Map<String, String> entries = this.mPBS.getAdditionalBraindumpEntries(workflow);
        entries.put("dag", this.getPathtoPMCFile(workflow));
        return entries;
    }

    /**
     * Returns the basename for the pmc file for the dag
     * 
     * @param dag the workflow
     * 
     * @return the basenmae
     */
    protected String pmcBasename( ADag dag ) {
        StringBuffer name = new StringBuffer();
        name.append(  dag.getLabel() ).append( "-" ).
             append( dag.dagInfo.index ).append( ".dag" );
        return name.toString();
    }
    

    /**
     * Returns the basename for the pmc file for the dag
     *
     * @param dag the workflow
     *
     * @return the basenmae
     */
    protected String getPathtoPMCFile( ADag dag ) {
        StringBuilder script = new StringBuilder();
        script.append( this.mSubmitFileDir ).append( File.separator ).
               append( this.pmcBasename(dag) );
        return script.toString();
    }





}
